import "@site/src/languages/highlight";

# 使用 self 参数的异同

&emsp;&emsp;在使用 TypeScriptToLua (TSTL) 进行 TypeScript 编写并编译为 Lua 时，由于两者在语言特性上的差异，开发者需要注意一些关键的行为变化和约定。本文将重点介绍 `self` 参数的处理. 回调函数的上下文参数. 以及如何避免常见的类型错误等内容。

#### `self` 参数存在的意义

&emsp;&emsp;在 TSTL 的默认配置中，所有函数默认会包含一个 `self` 参数，以保持与 JavaScript 中 `this` 关键字的行为一致。这个 `self` 参数用于指代调用函数的对象上下文。对于大部分 Lua 用户来说，这与 Lua 的 `:`（冒号）调用方法类似。

## 1. 注意事项

&emsp;&emsp;在 Dora SSR 使用的 TSTL 中，则通过启用了 `noImplicitSelf` 配置选项。使得所有的普通函数不再默认包含一个 `self` 参数，以进行 JavaScript 的 `this` 关键字的功能模拟。

#### 示例：

在 Dora SSR 中：

**输入 (TypeScript)**

```ts
function f() {}
function f2(this: any) {}
const a = () => {};
class C {
	method() {}
};
interface Item {
	method(): void;
};
const item: Item = {
	method() {}
};
```

**输出 (Lua)**

```lua
function f() end
function f2(self) end
local a = function() end

local C = __TS__Class()
C.name = "C"
function C.prototype.____constructor(self) end
function C.prototype.method(self) end -- 类成员依然有 self 参数

local item = {
	method = function(self) end -- 对象成员函数默认依然有 self 参数
}
```

&emsp;&emsp;注意：即使启用了 `noImplicitSelf`，类方法和对象成员函数依然会默认带有 `self`，除非你明确声明 `this: void`。

## 2. 如何显式移除不想要的 `self` 参数

&emsp;&emsp;当你希望主动声明移除 `self` 参数，尤其是为了与 Lua 代码进行交互时，可以使用 TypeScript 的 `this: void` 语法。这会告诉编译器在当前上下文中不允许使用 `this`，从而消除 `self` 参数。

### 2.1 在类方法中使用 `this: void`

&emsp;&emsp;在类的定义中，如果希望某些方法不使用 `self`，可以明确声明 `this: void`。

#### 示例：

**输入 (TypeScript)**

```ts
declare class Class {
	colon(arg: string): void;
	dot(this: void, arg: string): void;
}

const c = new Class();
c.colon("foo"); // 使用冒号调用
c.dot("foo"); // 使用点号调用
```

**输出 (Lua)**

```lua
local c = __TS__New(Class)
c:colon("foo")
c.dot("foo")
```

&emsp;&emsp;通过这种方式，你可以根据需求在类方法中控制是否生成 `self` 参数。

### 2.2 处理回调函数中的 `self` 参数

&emsp;&emsp;在常见的 Lua 库中，回调函数通常不会使用 `self` 参数。在编写与这些库交互的 TypeScript 代码时，务必确保回调函数的声明中不包含 `self` 参数。

#### 示例：

**输入 (TypeScript)**

```ts
type Callback = (this: void, arg: string) => void;

declare function useCallback(this: void, callback: Callback): void;

useCallback(arg => {
	print(arg);
});
```

**输出 (Lua)**

```lua
useCallback(function(arg)
  print(arg)
end)
```

&emsp;&emsp;在这个示例中，我们明确声明了回调函数不包含 `self` 参数，这样编译后的 Lua 代码中也不会生成多余的上下文参数。

### 2.3 使用 `@noSelf` 注解

&emsp;&emsp;如果希望某个类. 接口中的所有函数都不带上下文参数，可以使用 `@noSelf` 注解。这样可以避免在每个函数中手动指定 `this: void`。

**输入 (TypeScript)**

```ts
/** @noSelf **/
interface Item {
	foo(arg: string): void;
};
const item: Item = {
	foo(arg) {}
};
```

**输出 (Lua)**

```lua
local item = {
	foo = function(arg) end
}
```

&emsp;&emsp;你也可以通过在单个函数中指定 `this` 参数来覆盖 `@noSelf` 注解。例如：

**输入 (TypeScript)**

```ts
/** @noSelf **/
interface Item {
	foo(arg: string): void;
	bar(this: any, arg: string): void;
};
const item: Item = {
	foo(arg) {},
	bar(arg) {}
};
```

**输出 (Lua)**

```lua
local item = {
	foo = function(arg) end,
	bar = function(self, arg) end
}
```

## 3. 赋值错误与解决方案

&emsp;&emsp;在 TSTL 中，带有 `this: void` 的函数和普通函数之间是不可互相赋值的。如果你试图将一个带有上下文参数的函数赋值给一个不带上下文参数的函数类型，TSTL 会抛出错误。

#### 错误示例：

```typescript
declare function useCallback(cb: (this: void, arg: string) => void);

function callback(arg: string) {}
useCallback(callback);  // ❌ 错误
```

&emsp;&emsp;这种错误可以通过将函数包裹在箭头函数中解决：

#### 修正后：

```typescript
useCallback((arg) => callback(arg));
```

&emsp;&emsp;这样，TSTL 将不会尝试生成 `self` 参数，从而避免类型不匹配的问题。

## 4. 避免重载函数的上下文不一致

&emsp;&emsp;在 TypeScript 中，函数允许重载多个不同的签名。但是，在 TSTL 中，如果多个重载函数的签名的上下文类型不同（例如一个带 `this: void`，一个不带），将会导致编译错误。

#### 示例：

```ts
declare function useCallback(f: () => {}): void;

declare function callback(this: void, s: string, n: number): void;
declare function callback(s: string);

useCallback(callback);  // ❌ 错误：上下文类型不一致
```

&emsp;&emsp;为避免这些错误，最好避免上下文类型不一致的函数过载。
